using System;
using System.Collections.Generic;
using System.Text;
using Ricciolo.PaperBoy.Feeds.Rss;
using Interop = Microsoft.Feeds.Interop;
using System.Collections.ObjectModel;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Windows;
using System.Collections;

namespace Ricciolo.PaperBoy.Feeds
{
    public class FeedItem : NotifyItem, IFeedItem
    {

        private const string SlashNamespace = "http://purl.org/rss/1.0/modules/slash/";
        private const string AtomNamespace = "http://www.w3.org/2005/Atom";
        private const string WfwNamespace = "http://wellformedweb.org/CommentAPI/";
        private const string TrackBack = "http://madskills.com/public/xml/rss/module/trackback/";

        private int _localId;
        private Interop.IFeedItem feedItem;

        private string _title;
        private string _description;
        private string _author;
        private Uri _comments;
        private DateTime? _pubDate;
        private DateTime? _modified;
        private int? _commentsCount;
        private bool? _isRead;
        private string _category;
        private XmlElement xml;
        private Uri _link;
        private bool _skipMarkAsRead;

        private XmlNamespaceManager nsManager;

        private readonly Feed _feed;

        internal FeedItem(Feed feed, int localId)
            : base(feed.feedsManager.dispatcher)
        {
            if (feed == null)
                throw new ArgumentNullException("feed");

            this._feed = feed;
            this._localId = localId;
            this.RefreshData();
        }

        public void Refresh()
        {
            RefreshData();

            // TODO: evaluate if raise all property or not
            this.OnPropertyChanged("Author");
            this.OnPropertyChanged("Comments");
            this.OnPropertyChanged("Description");
            this.OnPropertyChanged("DownloadUrl");
            this.OnPropertyChanged("IsRead");
            this.OnPropertyChanged("IsNotRead");
            this.OnPropertyChanged("Link");
            this.OnPropertyChanged("LastDownloadTime");
            this.OnPropertyChanged("Modified");
            this.OnPropertyChanged("PubDate");
            this.OnPropertyChanged("Title");
            this.OnPropertyChanged("Guid");
            this.OnPropertyChanged("DownloadUrl");
            this.OnPropertyChanged("Category");
            this.OnPropertyChanged("CommentsCount");
            this.OnPropertyChanged("Comments");
            this.OnPropertyChanged("CommentRss");
        }

        private void RefreshData()
        {
            _title = null;
            _description = null;
            _author = null;
            _comments = null;
            _pubDate = null;
            _modified = null;
            _link = null;
            _commentsCount = null;
            _isRead = null;
            _category = null;

            // Removed to load only when needed
            EnsureInteropItem();

        }

        private void EnsureInteropItem()
        {
            if (feedItem == null)
            {
                feedItem = (Interop.IFeedItem)Feed.feed.GetItem(this.LocalId);

                // Load xml
                string xml = feedItem.Xml(Microsoft.Feeds.Interop.FEEDS_XML_INCLUDE_FLAGS.FXIF_CF_EXTENSIONS);
                XmlDocument doc = new XmlDocument();
                try
                {
                    doc.LoadXml(xml);
                    this.nsManager = new XmlNamespaceManager(doc.NameTable);
                    this.nsManager.AddNamespace("slash", SlashNamespace);
                    this.nsManager.AddNamespace("wfw", WfwNamespace);
                    this.xml = doc.DocumentElement;
                }
                catch (XmlException)
                {
                    this.xml = null;
                }
            }
        }

        #region Properties

        /// <summary>
        /// Get the owner feed
        /// </summary>
        public Feed Feed
        {
            get { return _feed; }
        }

        /// <summary>
        /// Retrieves the assigned ID of the feed item
        /// </summary>
        public int LocalId
        {
            get { return _localId; }
        }

        /// <summary>
        /// Retrieves the author from the feed item
        /// </summary>
        public string Author
        {
            get
            {
                if (_author == null)
                {
                    //EnsureInteropItem();
                    _author = this.feedItem.Author ?? "(none)";
                }
                return _author;
            }
        }

        /// <summary>
        /// Retrieves the URL of the comments page from the feed item
        /// </summary>
        public Uri Comments
        {
            get
            {
                if (_comments == null)
                {
                    //EnsureInteropItem();
                    if (!Uri.TryCreate(this.feedItem.Comments, UriKind.RelativeOrAbsolute, out _comments))
                    {
                        XmlNode node = null;
                        if (this.xml != null)
                            node = this.xml.SelectSingleNode("/item/wfw:comments", this.nsManager);
                        if (node != null)
                            Uri.TryCreate(node.InnerText, UriKind.RelativeOrAbsolute, out _comments);
                    }
                }
                return _comments;
            }
        }

        /// <summary>
        /// Get RSS uri comments for this item
        /// </summary>
        public Uri CommentRss
        {
            get
            {
                XmlNode node = null;
                Uri u;
                if (this.xml != null)
                    node = this.xml.SelectSingleNode("/item/wfw:commentRss", this.nsManager);
                if (node != null && Uri.TryCreate(node.InnerText, UriKind.Absolute, out u))
                    return u;

                return null;
            }
        }

        /// <summary>
        /// Retrieves the number of comments from the feed item
        /// </summary>
        public int CommentsCount
        {
            get
            {
                if (!_commentsCount.HasValue)
                {
                    //EnsureInteropItem();
                    XmlNode node = null;
                    if (xml != null)
                        node = this.xml.SelectSingleNode("/item/slash:comments", this.nsManager);
                    int c;
                    if (node == null || !Int32.TryParse(node.InnerText, out c))
                        c = 0;
                    _commentsCount = c;
                }
                return _commentsCount.Value;
            }
        }

        /// <summary>
        /// Retrieves a phrase or sentence that describes the feed or item
        /// </summary>
        public string Description
        {
            get
            {
                if (_description == null)
                {
                    //EnsureInteropItem();
                    _description = this.feedItem.Description;
                }
                return _description;
            }
        }

        /// <summary>
        /// Retrieves category that describes the feed or item
        /// </summary>
        public string Category
        {
            get
            {
                if (_category == null)
                {
                    //EnsureInteropItem();
                    XmlNode node = null;
                    if (xml != null)
                        node = xml.SelectSingleNode("/item/category");
                    if (node != null)
                        _category = node.InnerText;
                    else
                        _category = "(none)";
                }
                return _category;
            }
        }

        /// <summary>
        /// Retrieves the URL from which the feed, item, or enclosure was downloaded
        /// </summary>
        public Uri DownloadUrl
        {
            get { return new Uri(this.feedItem.DownloadUrl); }
        }

        /// <summary>
        /// Retrieves the guid from the feed item
        /// </summary>
        public String Guid
        {
            get
            {
                if (this.feedItem.Guid != null)
                    return this.feedItem.Guid;
                else
                    return this.Link.ToString();
            }
        }

        /// <summary>
        /// Sets or retrieves the read status of the feed item
        /// </summary>
        public bool IsRead
        {
            get
            {
                if (!_isRead.HasValue)
                {
                    //EnsureInteropItem();
                    if (this.Exists)
                        _isRead = this.feedItem.IsRead;
                    else
                        _isRead = true;
                }

                return _isRead.Value;
            }
            set
            {
                if (this.IsRead != value && this.Exists)
                {
                    this.SkipMarkAsRead = false;
                    this.feedItem.IsRead = value;
                    this._isRead = value;
                    this.OnPropertyChanged("IsRead");
                    this.OnPropertyChanged("IsNotRead");
                }
            }
        }

        public bool Exists
        {
            get
            {
                try
                {
                    string s = this.feedItem.Guid;

                    return true;
                }
                catch (FileNotFoundException)
                {
                    return false;
                }
            }
        }

        public bool IsNotRead
        {
            get { return !this.IsRead; }
        }

        /// <summary>
        /// Retrieves the date and time of the last successful download
        /// </summary>
        public DateTime LastDownloadTime
        {
            get { return this.feedItem.LastDownloadTime; }
        }

        /// <summary>
        /// Retrieves the URL from the feed item
        /// </summary>
        public Uri Link
        {
            get
            {
                if (_link == null)
                {
                    //EnsureInteropItem();
                    if (this.feedItem.Link != null)
                        _link = new Uri(this.feedItem.Link);
                    else
                        _link = new Uri("about:blank");
                }
                return _link;
            }
        }

        /// <summary>
        /// Retrieves the date and time of the last modification for this item
        /// </summary>
        public DateTime Modified
        {
            get
            {
                if (!_modified.HasValue)
                {
                    //EnsureInteropItem();
                    _modified = this.feedItem.Modified;
                }
                return _modified.Value;
            }
        }

        /// <summary>
        /// Retrieves the publication date and time of the feed or item
        /// </summary>
        public DateTime PubDate
        {
            get
            {
                if (!_pubDate.HasValue)
                {
                    //EnsureInteropItem();
                    _pubDate = this.feedItem.PubDate;
                }
                return _pubDate.Value;
            }
        }
        /// <summary>
        /// Retrieves the title from the feed or feed item
        /// </summary>
        public string Title
        {
            get
            {
                if (_title == null)
                {
                    //EnsureInteropItem();
                    if (this.Exists)
                        _title = System.Web.HttpUtility.HtmlDecode(this.feedItem.Title);
                }
                return _title;
            }
        }

        /// <summary>
        /// Set to skip next mark as read
        /// </summary>
        public bool SkipMarkAsRead
        {
            get { return _skipMarkAsRead; }
            set { _skipMarkAsRead = value; }
        }

        #endregion

        /// <summary>
        /// Deletes the feed, folder, or item
        /// </summary>
        public void Delete()
        {
            this.feedItem.Delete();
        }
    }

    public class FeedItemCollection : ObservableCollection<FeedItem>, IRefresh
    {

        private readonly Feed _feed;
        private bool deferRefresh;
        private bool _isLoading;
        private bool _mustReload;

        private delegate void MethodInvoker();

        internal FeedItemCollection(Feed feed)
        {
            if (feed == null)
                throw new ArgumentNullException("feed");

            this._feed = feed;
            this._mustReload = true;
        }

        public Feed Feed
        {
            get { return this._feed; }
        }

        public bool MustReload
        {
            get { return _mustReload; }
            set { _mustReload = value; }
        }

        public bool IsLoading
        {
            get { return _isLoading; }
            private set
            {
                if (_isLoading != value)
                {
                    _isLoading = value;
                    OnPropertyChanged("IsLoading");
                }
            }
        }

        private void OnRefreshCallback(object state)
        {
            bool check = (bool)state;
            this.Refresh(check);
        }

        private void OnPropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (!deferRefresh)
                base.OnPropertyChanged(e);
        }

        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (!deferRefresh)
                base.OnCollectionChanged(e);
        }

        public bool Refresh(bool check)
        {
            try
            {
                this.IsLoading = true;
                deferRefresh = true;
                try
                {
                    return this.RefreshInternal(check);
                }
                catch (ThreadAbortException)
                {
                    this.Clear();
                    return false;
                }
            }
            finally
            {
                _mustReload = false;
                this.IsLoading = false;
                deferRefresh = false;
            }
        }

        private bool RefreshInternal(bool check)
        {
            Debug.WriteLine("Refresh " + this._feed.Path);

            int added = 0;
            List<FeedItem> newItems = new List<FeedItem>();

            // Check if items was changed
            if (this.Count != this._feed.feed.ItemCount)
            {
                List<FeedItem> list = null;
                if (check)
                    list = new List<FeedItem>(this);

                // Populate collection
                foreach (Interop.IFeedItem f in (Interop.IFeedsEnum)this._feed.feed.Items)
                {
                    if (check)
                    {
                        FeedItem fi = FindFeedItemByLocalId(f.LocalId);
                        if (fi != null)
                        {
                            list.Remove(fi);
                            fi.Refresh();
                            continue;
                        }
                    }

                    // Add if not exist
                    FeedItem ff = new FeedItem(_feed, f.LocalId);
                    this.Add(ff);

                    added++;

                }

                // Remove last items
                if (check)
                {
                    foreach (FeedItem i in list)
                        this.Remove(i);
                }

                if (deferRefresh && added > 0)
                    RaiseAdded(newItems);
            }
            else
            {
                // TODO: this method is calling n times for each feed item changed
                // It's very slow
                //for (int x = 0; x < this.Count; x++)
                //{
                //}
            }

            Debug.WriteLine("End refresh " + this._feed.Path);
            Debug.WriteLine("New items: " + newItems.Count);

            return true;
        }

        private void RaiseAdded(List<FeedItem> newItems)
        {
            // Invoke events on main thread
            this._feed.feedsManager.dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.DataBind, new MethodInvoker(delegate
            {
                base.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                base.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }));
        }

        private FeedItem FindFeedItemByLocalId(int localId)
        {
            for (int x = 0; x < this.Count; x++)
                if (this[x].LocalId == localId)
                {
                    return this[x];
                }
            return null;
        }

        protected override void ClearItems()
        {
            this._mustReload = true;

            base.ClearItems();
        }

        [Obsolete()]
        public void LoadAsync(bool check)
        {
            lock (this)
            {
                if (this.IsLoading) return;

                ThreadPool.QueueUserWorkItem(new WaitCallback(OnRefreshCallback), check);
            }
            //OnRefreshCallback(null);
        }

        #region IRefresh Members

        void IRefresh.Refresh()
        {
            this.Refresh(false);
        }

        #endregion
    }

    public class MergedCollection : INotifyCollectionChanged, IWeakEventListener, IList, IRefresh
    {

        private NotifyCollectionChangedEventHandler _collectionChanged;
        private INotifyCollectionChanged[] collections;

        public MergedCollection(INotifyCollectionChanged[] collections)
        {
            if (collections == null)
                throw new ArgumentNullException("collections");
            this.collections = collections;

            foreach (INotifyCollectionChanged c in collections)
                CollectionChangedEventManager.AddListener(c, this);
        }

        public INotifyCollectionChanged[] Collections
        {
            get { return this.collections; }
        }

        #region INotifyCollectionChanged Members

        event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
        {
            add { _collectionChanged += value; }
            remove { _collectionChanged -= value; }
        }

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (_collectionChanged != null)
                _collectionChanged(this, e);
        }

        #endregion

        #region IWeakEventListener Members

        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (e is NotifyCollectionChangedEventArgs)
            {
                NotifyCollectionChangedEventArgs ne = (NotifyCollectionChangedEventArgs)e;
                OnCollectionChanged(ne);
                return true;
            }

            return false;
        }

        #endregion

        #region IList Members

        int IList.Add(object value)
        {
            throw new NotSupportedException();
        }

        void IList.Clear()
        {
            for (int x = 0; x < this.Collections.Length; x++)
            {
                FeedItemCollection l = (FeedItemCollection)this.Collections[x];
                l.Clear();
            }

        }

        bool IList.Contains(object value)
        {
            throw new NotSupportedException();
        }

        int IList.IndexOf(object value)
        {
            int c = 0;
            for (int x = 0; x < this.collections.Length; x++)
            {
                IList l = (IList)this.collections[x];
                int i = l.IndexOf(value);
                if (i >= 0)
                    return i + c;
                c += l.Count;
            }

            return -1;
        }

        void IList.Insert(int index, object value)
        {
            throw new NotSupportedException();
        }

        bool IList.IsFixedSize
        {
            get { return true; }
        }

        bool IList.IsReadOnly
        {
            get { return true; }
        }

        void IList.Remove(object value)
        {
            throw new NotSupportedException();
        }

        void IList.RemoveAt(int index)
        {
            throw new NotSupportedException();
        }

        object IList.this[int index]
        {
            get
            {
                int c = 0;
                for (int x = 0; x < this.collections.Length; x++)
                {
                    IList l = (IList)this.collections[x];
                    if (index < c + l.Count)
                        return l[index - c];
                    c += l.Count;
                }
                return null;
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        #endregion

        #region ICollection Members

        void ICollection.CopyTo(Array array, int index)
        {
            throw new NotSupportedException();
        }

        int ICollection.Count
        {
            get
            {
                int count = 0;
                // Sum child count
                for (int x = 0; x < this.collections.Length; x++)
                {
                    if (this.collections[x] is ICollection)
                        count += ((ICollection)this.collections[x]).Count;
                }

                return count;
            }
        }

        bool ICollection.IsSynchronized
        {
            get { return false; }
        }

        object ICollection.SyncRoot
        {
            get { return this; }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            // Sum child count
            for (int x = 0; x < this.collections.Length; x++)
            {
                if (this.collections[x] is IEnumerable)
                    foreach (object o in (IEnumerable)this.collections[x])
                        yield return o;
            }
        }

        #endregion

        #region IRefresh Members

        public void Refresh()
        {
            try
            {
                // Reload data if needed
                for (int x = 0; x < this.Collections.Length; x++)
                {
                    FeedItemCollection l = (FeedItemCollection)this.Collections[x];
                    if (l.MustReload)
                        l.Refresh(false);
                }
            }
            catch (ThreadAbortException)
            {
                for (int x = 0; x < this.Collections.Length; x++)
                {
                    FeedItemCollection l = (FeedItemCollection)this.Collections[x];
                    l.Clear();
                }
            }
        }

        #endregion
    }
}
